The Reading Ruler
Key Features:
- Auto-Save and Load: Automatically saves and loads the selected area, style, theme, transparency, and visibility state.
- Theme Selection and Opacity Control: You can select from predefined themes or use a custom color, along with an opacity slider to control the transparency.
- Persistent Ruler Settings: Your preferences will be restored upon page reload.
How It Works:
- Hover near the bottom to reveal the control panel.
- Click "📐" to select an area on the page.
- Click "🔄" to toggle between the two styles: one with overlays and the other with a transparent block.
- Choose a color theme or pick a custom color using the color picker.
- Click "👁️" to toggle the ruler's visibility on and off.
- The script auto-saves your preferences and restores them when the page reloads.
This should now include auto-save and reload functionality while keeping all the features from your version. Let me know how it works!
// ==UserScript==
// @name V.0.3.0 Working with Auto-Save/Load
// @namespace http://your-namespace-here
// @version 3.0 with Auto-Save/Load
// @description A reading ruler with theme selection, opacity control, and color picker for custom RGB values, and auto-save/load of settings.
// @match *://*/*
// @grant none
// ==/UserScript==
(function () {
let selectedArea = null;
let rulerVisible = false;
let currentStyle = 'style1'; // Default to style 1
let currentTheme = 'rgba(255, 255, 0, 0.3)'; // Default theme
let currentTransparency = 0.3; // Default transparency
// Load saved settings from localStorage
function loadSettings() {
const savedSettings = JSON.parse(localStorage.getItem('readingRulerSettings'));
if (savedSettings) {
selectedArea = savedSettings.selectedArea;
currentStyle = savedSettings.currentStyle || 'style1';
currentTheme = savedSettings.currentTheme || 'rgba(255, 255, 0, 0.3)';
currentTransparency = savedSettings.currentTransparency || 0.3;
rulerVisible = savedSettings.rulerVisible || false;
if (rulerVisible && selectedArea) {
showRuler(true); // Reload the ruler with saved settings
}
}
}
// Save settings to localStorage
function saveSettings() {
const settings = {
selectedArea,
currentStyle,
currentTheme,
currentTransparency,
rulerVisible
};
localStorage.setItem('readingRulerSettings', JSON.stringify(settings));
}
// Preset color themes
const colorThemes = {
yellow: 'rgba(255, 255, 0, 0.3)',
blue: 'rgba(0, 122, 255, 0.3)',
green: 'rgba(52, 199, 89, 0.3)',
custom: '', // Placeholder for custom color
};
// Create the ruler element
const ruler = document.createElement('div');
ruler.id = 'reading-ruler';
Object.assign(ruler.style, {
position: 'fixed', // Fixed to screen, not the page
height: '30px',
backgroundColor: currentTheme,
pointerEvents: 'none',
zIndex: '9999',
borderRadius: '5px',
display: 'none', // Initially hidden
opacity: currentTransparency,
transition: 'opacity 0.3s ease-in-out, transform 0.3s ease-in-out', // Smooth animations
transform: 'scale(0.95)', // Start smaller for smooth transition
});
document.body.appendChild(ruler);
// Overlay elements for style 1 (lines above and below reading area)
const topOverlay = document.createElement('div');
const bottomOverlay = document.createElement('div');
Object.assign(topOverlay.style, bottomOverlay.style, {
position: 'fixed', // Fixed to screen, not the page
backgroundColor: 'rgba(0, 0, 0, 0.6)', // Dark overlay for lines above and below
zIndex: '9998',
display: 'none', // Initially hidden
opacity: '0',
transition: 'opacity 0.3s ease-in-out',
});
document.body.appendChild(topOverlay);
document.body.appendChild(bottomOverlay);
// Create the minimalist control panel
const controlPanel = document.createElement('div');
controlPanel.id = 'control-panel';
Object.assign(controlPanel.style, {
position: 'fixed',
bottom: '-80px', // Initially hidden off-screen
left: '50%',
transform: 'translateX(-50%)',
zIndex: '10000',
display: 'flex',
alignItems: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.8)',
padding: '12px',
borderRadius: '20px',
color: '#fff',
cursor: 'pointer',
opacity: '0.9',
transition: 'bottom 0.3s ease-in-out',
});
// Show control panel when mouse is near bottom of the screen
document.addEventListener('mousemove', (e) => {
if (e.clientY > window.innerHeight - 100) {
controlPanel.style.bottom = '20px'; // Slide up to visible
} else {
controlPanel.style.bottom = '-80px'; // Slide down to hide
}
});
// Add the "Select Area" button (with an icon)
const selectAreaButton = document.createElement('button');
selectAreaButton.innerHTML = '📐'; // Icon for selection
Object.assign(selectAreaButton.style, {
backgroundColor: 'transparent',
border: 'none',
color: '#fff',
fontSize: '24px',
cursor: 'pointer',
margin: '0 10px',
});
selectAreaButton.onclick = startAreaSelection;
controlPanel.appendChild(selectAreaButton);
// Style switcher (Style 1 vs Style 2) - Icon based
const styleSwitcher = document.createElement('button');
styleSwitcher.innerHTML = '🔄'; // Icon for style switch
Object.assign(styleSwitcher.style, {
backgroundColor: 'transparent',
border: 'none',
color: '#fff',
fontSize: '24px',
cursor: 'pointer',
margin: '0 10px',
});
styleSwitcher.onclick = () => {
currentStyle = currentStyle === 'style1' ? 'style2' : 'style1';
updateRulerStyle();
saveSettings(); // Save settings on style change
};
controlPanel.appendChild(styleSwitcher);
// Create the "Enable/Disable Ruler" button (Icon based)
const toggleRulerButton = document.createElement('button');
toggleRulerButton.innerHTML = '👁️'; // Icon for visibility toggle
Object.assign(toggleRulerButton.style, {
backgroundColor: 'transparent',
border: 'none',
color: '#fff',
fontSize: '24px',
cursor: 'pointer',
margin: '0 10px',
});
toggleRulerButton.onclick = () => {
if (rulerVisible) {
hideRuler();
} else {
showRuler();
}
saveSettings(); // Save visibility state
};
controlPanel.appendChild(toggleRulerButton);
// Theme selection button (dropdown)
const themeDropdown = document.createElement('select');
Object.assign(themeDropdown.style, {
backgroundColor: 'transparent',
border: '1px solid #fff',
color: '#fff',
fontSize: '16px',
cursor: 'pointer',
margin: '0 10px',
});
// Add theme options
Object.keys(colorThemes).forEach((key) => {
const option = document.createElement('option');
option.value = key;
option.innerText = key.charAt(0).toUpperCase() + key.slice(1);
themeDropdown.appendChild(option);
});
themeDropdown.onchange = function () {
const selectedTheme = themeDropdown.value;
if (selectedTheme === 'custom') {
// When custom is selected, use the color picker
colorPicker.click();
} else {
currentTheme = colorThemes[selectedTheme];
}
updateRulerStyle();
saveSettings(); // Save theme change
};
controlPanel.appendChild(themeDropdown);
// Color picker for custom color
const colorPicker = document.createElement('input');
colorPicker.type = 'color';
colorPicker.style.display = 'none'; // Hidden, will trigger when custom is selected
colorPicker.oninput = function () {
const colorValue = colorPicker.value;
currentTheme = `rgba(${parseInt(colorValue.slice(1, 3), 16)}, ${parseInt(colorValue.slice(3, 5), 16)}, ${parseInt(colorValue.slice(5, 7), 16)}, ${currentTransparency})`;
updateRulerStyle();
saveSettings(); // Save custom theme
};
document.body.appendChild(colorPicker);
// Opacity slider
const opacitySlider = document.createElement('input');
opacitySlider.type = 'range';
opacitySlider.min = '0.1';
opacitySlider.max = '1';
opacitySlider.step = '0.1';
opacitySlider.value = currentTransparency;
Object.assign(opacitySlider.style, {
margin: '0 10px',
cursor: 'pointer',
});
opacitySlider.oninput = function () {
currentTransparency = this.value;
updateRulerStyle();
saveSettings(); // Save opacity setting
};
controlPanel.appendChild(opacitySlider);
// Append the control panel to the body
document.body.appendChild(controlPanel);
// Functions
function
showRuler(isLoading = false) {
if (selectedArea) {
ruler.style.display = 'block';
ruler.style.opacity = currentTransparency;
ruler.style.transform = 'scale(1)';
ruler.style.top = `${selectedArea.top}px`; // Set ruler top
ruler.style.left = `${selectedArea.left}px`; // Set ruler left
ruler.style.width = `${selectedArea.width}px`; // Set ruler width
topOverlay.style.display = currentStyle === 'style1' ? 'block' : 'none';
bottomOverlay.style.display = currentStyle === 'style1' ? 'block' : 'none';
topOverlay.style.opacity = '1';
bottomOverlay.style.opacity = '1';
rulerVisible = true;
if (!isLoading) saveSettings();
updateRulerStyle();
} else {
alert("Please select an area first.");
}
}
function hideRuler() {
ruler.style.opacity = '0';
ruler.style.transform = 'scale(0.95)';
topOverlay.style.opacity = '0';
bottomOverlay.style.opacity = '0';
setTimeout(() => {
ruler.style.display = 'none';
topOverlay.style.display = 'none';
bottomOverlay.style.display = 'none';
}, 300); // Wait for animation to complete
rulerVisible = false;
saveSettings();
}
function updateRulerStyle() {
// Apply transparency and theme
ruler.style.backgroundColor = currentTheme;
ruler.style.opacity = currentTransparency;
// Adjust for focus style
if (currentStyle === 'style1') {
topOverlay.style.display = 'block';
bottomOverlay.style.display = 'block';
ruler.style.border = 'none';
} else {
topOverlay.style.display = 'none';
bottomOverlay.style.display = 'none';
ruler.style.border = `1px solid rgba(0, 0, 0, ${currentTransparency})`;
}
// Update overlay positions based on viewport
if (selectedArea) {
topOverlay.style.height = `${selectedArea.top}px`;
bottomOverlay.style.top = `${selectedArea.bottom}px`;
bottomOverlay.style.height = `${window.innerHeight - selectedArea.bottom}px`;
}
}
function startAreaSelection() {
const overlay = createSelectionOverlay();
document.body.appendChild(overlay);
let startX, startY;
overlay.addEventListener('mousedown', (e) => {
startX = e.clientX;
startY = e.clientY;
const selectionBox = document.createElement('div');
selectionBox.style.position = 'fixed'; // Fixed to viewport
selectionBox.style.border = '2px dashed #007AFF';
selectionBox.style.backgroundColor = 'rgba(0, 122, 255, 0.1)';
selectionBox.style.zIndex = '100001';
document.body.appendChild(selectionBox);
const onMouseMove = (moveEvent) => {
const currentX = moveEvent.clientX;
const currentY = moveEvent.clientY;
const width = Math.abs(currentX - startX);
const height = Math.abs(currentY - startY);
selectionBox.style.left = `${Math.min(startX, currentX)}px`;
selectionBox.style.top = `${Math.min(startY, currentY)}px`;
selectionBox.style.width = `${width}px`;
selectionBox.style.height = `${height}px`;
};
const onMouseUp = (mouseUpEvent) => {
selectedArea = {
top: Math.min(startY, mouseUpEvent.clientY),
bottom: Math.max(startY, mouseUpEvent.clientY),
left: Math.min(startX, mouseUpEvent.clientX),
right: Math.max(startX, mouseUpEvent.clientX),
width: Math.abs(mouseUpEvent.clientX - startX),
height: Math.abs(mouseUpEvent.clientY - startY)
};
document.body.removeChild(overlay);
document.body.removeChild(selectionBox);
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
// Constrain the ruler within the selected area
ruler.style.top = `${selectedArea.top}px`;
ruler.style.left = `${selectedArea.left}px`;
ruler.style.width = `${selectedArea.width}px`;
ruler.style.height = `${selectedArea.height}px`;
// Update overlays for style 1
topOverlay.style.height = `${selectedArea.top}px`;
bottomOverlay.style.top = `${selectedArea.bottom}px`;
bottomOverlay.style.height = `${window.innerHeight - selectedArea.bottom}px`;
showRuler(); // Automatically show ruler after selection
saveSettings(); // Save the selected area
};
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
});
}
function createSelectionOverlay() {
const overlay = document.createElement('div');
Object.assign(overlay.style, {
position: 'fixed', // Fixed to viewport
top: '0',
left: '0',
width: '100%',
height: '100%',
backgroundColor: 'rgba(0, 0, 0, 0.1)',
zIndex: '100000',
cursor: 'crosshair',
});
return overlay;
}
// Load saved settings on page load
loadSettings();
})();